Skip to content

Rebuild GUI menus on a config-driven engine (guis.yml)#46

Open
rossnoah wants to merge 7 commits into
mainfrom
t3code/configurable-gui-system-redesign
Open

Rebuild GUI menus on a config-driven engine (guis.yml)#46
rossnoah wants to merge 7 commits into
mainfrom
t3code/configurable-gui-system-redesign

Conversation

@rossnoah

@rossnoah rossnoah commented Mar 12, 2026

Copy link
Copy Markdown
Owner

Summary

Rebuilds the GUI layer on a config-driven engine. Every menu the plugin opens is defined in a new guis.yml (layout, items, text, click actions), interpreted by ConfigurableGuiService; behavior stays in code as named components and action types the config refers to. GUI becomes a thin facade so all existing entry points keep working.

This branch was rewritten from scratch on top of current main — the original version predated i18n, max-kits pagination, and the editor save-during-redraw fix, and rebasing it would have reverted those. What the rework preserves and how:

  • Editor saving keeps main's proven session model: an EditorSession registered on open, flushed by InventoryCloseEvent or before opening the next menu (canvas redraw navigation reuses the open inventory and never fires a close event). Saves read the closing Bukkit inventory directly; the data slots come from the menu's own config, in declared order.
  • i18n: all default text routes through the lang system via %lang:key% placeholders, so the 14 bundled translations still apply; server owners can restyle or hardcode any text per menu. PlaceholderAPI placeholders work anywhere when installed.
  • max-kits pagination is a main-menu component: kit slot numbers derive from the page context, arrows render only where valid, back buttons return to the page a kit lives on, and /kit always starts at page 1.
  • Config lifecycle: guis.yml carries a config-version; menus missing from a user's file fall back per-GUI to the bundled copy, and a file with an older schema is archived to guis.yml.v<N>.bak and regenerated with a warning instead of half-working.
  • Repeated menu blocks (armor indicators, editor tools) are shared via YAML anchors, with a test proving YamlConfiguration flattens merge keys.

Review process

The diff went through two multi-agent review passes with fixes applied:

  • a 4-angle cleanup pass (reuse / simplification / efficiency / altitude)
  • a 3-angle correctness pass (line-by-line, removed-behavior audit vs old GUI code, cross-file caller trace)

Notable catches folded in: a failed inspect-delete no longer resurrects a kit another admin deleted; kit-room page indexes clamp instead of throwing IndexOutOfBoundsException; admin feedback paths and click-sound/keyboard-click semantics match the old menus; open-gui actions forward only explicitly declared context keys.

Testing

  • 150 unit tests pass, including new coverage for slot parsing, placeholder resolution (with a regression test for action arguments like slot: "%slot%"), and invariants on the bundled config (41 kit / 27 enderchest / 45 kit-room data slots, template-anchor flattening).
  • Boot-verified three times on a live Paper 26.2 server: clean enable, guis.yml extraction, per-GUI defaults fallback, and the v1→v2 archive-and-regenerate migration end-to-end.
  • Not yet done: an in-game click-through of the editor flows (edit→close→reopen, back-button saves, inspect edit/delete, kit-room save, pagination with max-kits > 9). Recommended before merge — headless verification can't click menus.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a configuration-driven GUI system powered by guis.yml, covering main kit hub, kit/public kit editors, enderchest editing, view-only enderchest, kit room browsing, public kit browser/viewer, and admin inspect menus.
  • Refactor
    • Centralized GUI opening and editor-session handling via a dedicated GUI service; key commands and menu entry points now delegate to it.
    • Introduced GUI config and text/slot helper utilities for consistent, configurable rendering.
  • Bug Fixes
    • Improved editor persistence when closing/switching menus.
  • Tests
    • Added unit tests for GUI config parsing and text/slot placeholder expansion.

@cursor

cursor Bot commented Mar 12, 2026

Copy link
Copy Markdown

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@coderabbitai

coderabbitai Bot commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 001c119b-3722-4c60-b260-6250dd0eb34f

📥 Commits

Reviewing files that changed from the base of the PR and between 850f224 and 14ac242.

📒 Files selected for processing (4)
  • src/main/java/dev/noah/perplayerkit/gui/configurable/GuiSlots.java
  • src/main/resources/guis.yml
  • src/test/java/dev/noah/perplayerkit/gui/configurable/BundledGuisConfigTest.java
  • src/test/java/dev/noah/perplayerkit/gui/configurable/GuiSlotsTest.java
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/test/java/dev/noah/perplayerkit/gui/configurable/GuiSlotsTest.java
  • src/test/java/dev/noah/perplayerkit/gui/configurable/BundledGuisConfigTest.java
  • src/main/java/dev/noah/perplayerkit/gui/configurable/GuiSlots.java
  • src/main/resources/guis.yml

📝 Walkthrough

Walkthrough

A YAML-driven GUI subsystem is added with configurable rendering, actions, editor sessions, persistence, and placeholder handling. Legacy GUI entry points and the view-only enderchest command delegate to the new service, which is initialized during plugin startup.

Changes

Configurable GUI subsystem

Layer / File(s) Summary
GUI contracts and configuration
src/main/java/dev/noah/perplayerkit/gui/configurable/*, src/main/resources/guis.yml
Adds GUI context, editor sessions, slot and text utilities, configuration loading and migration, plus definitions for menus, editors, inspections, kit rooms, and public kits.
Rendering and action runtime
src/main/java/dev/noah/perplayerkit/gui/configurable/ConfigurableGuiService.java
Implements GUI entry points, configured rendering, variants, permissions, paging, item/text resolution, click actions, imports, repairs, deletes, and navigation context.
Editor lifecycle and integration
src/main/java/dev/noah/perplayerkit/PerPlayerKit.java, src/main/java/dev/noah/perplayerkit/gui/GUI.java, src/main/java/dev/noah/perplayerkit/gui/GuiCompat.java, src/main/java/dev/noah/perplayerkit/commands/kits/EnderchestCommand.java, src/main/java/dev/noah/perplayerkit/listeners/KitMenuCloseListener.java, src/main/java/dev/noah/perplayerkit/KitRoomDataManager.java
Initializes the service, delegates legacy GUI and enderchest entry points, forwards inventory-close persistence, exposes title-update compatibility methods, and adds kit-room page counting.
Configuration and utility validation
src/test/java/dev/noah/perplayerkit/gui/configurable/*
Tests bundled GUI structure, YAML templates and permissions, slot parsing, invalid-input handling, placeholder expansion, and numeric resolution.

Estimated code review effort: 4 (Complex) | ~70 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.49% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: rebuilding GUI menus around a config-driven guis.yml engine.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/configurable-gui-system-redesign

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/main/java/dev/noah/perplayerkit/gui/GUI.java (1)

32-40: Don't keep the legacy state helpers as silent no-ops.

If any legacy caller survives the migration, these methods now fail silently instead of surfacing the bad path. src/main/java/dev/noah/perplayerkit/listeners/KitMenuCloseListener.java still depends on this API, so I'd either remove the compatibility surface or deprecate it with an explicit failure/message while the old flow is being retired.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/dev/noah/perplayerkit/gui/GUI.java` around lines 32 - 40, These
legacy state helper methods (setInspectTarget, getAndRemoveInspectTarget,
removeKitDeletionFlag) currently no-op and silently hide callers like
KitMenuCloseListener; update them to explicitly fail fast by throwing an
UnsupportedOperationException (or at minimum log a clear warning and throw) with
a message directing callers to the new API/flow so surviving callers are
surfaced during testing, or alternatively implement the original behavior if
backward compatibility must be preserved; ensure the exception message names the
method (e.g., "GUI.setInspectTarget") and references KitMenuCloseListener
migration guidance.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/dev/noah/perplayerkit/gui/configurable/ConfigurableGuiService.java`:
- Around line 266-287: renderPublicKitList currently only displays up to
slots.size() items, leaving PublicKit entries past that inaccessible; modify
renderPublicKitList to support pagination by reading a page index from
baseContext (e.g., "public_kit_page"), computing int start = page * slots.size()
and iterating from start to Math.min(start + slots.size(), publicKits.size())
when pulling from KitManager.get().getPublicKitList(), and render Prev/Next
controls (using applyVariant on dedicated control slots) that reopen the GUI
with updated "public_kit_page" in the GuiContext; alternatively, if you prefer
rejecting oversized configs, add a validation path that checks if
publicKits.size() > maxVisible (slots.size()) and logs/throws a clear config
validation error instead of silently truncating.
- Around line 1066-1077: resolveInt currently parses string values literally and
immediately returns defaultValue on NumberFormatException; instead, if the
string isn't a plain integer (e.g. contains a placeholder like "%slot%") or
parsing fails, try resolving the value from the GuiContext using the provided
fallbackContextKey before returning defaultValue. Update resolveInt to: when
rawValue is a String that fails Integer.parseInt, call
context.get(fallbackContextKey) (or the appropriate accessor on GuiContext) and
attempt to parse that result (or its toString()) to an int; also if rawValue is
null, try the same context lookup; only return defaultValue if both direct
parsing and the context-resolved value are unavailable or non-numeric. Use the
resolveInt function name and the fallbackContextKey/GuiContext symbols to locate
and modify the logic.

In `@src/main/resources/guis.yml`:
- Around line 48-49: The two label strings "<gray>● Left click to load
kit</gray>" and "<gray>● Right click to edit kit</gray>" are incorrect for the
enderchest menu; update them to say "enderchest" (e.g., "<gray>● Left click to
load enderchest</gray>" and "<gray>● Right click to edit enderchest</gray>") and
search the file for any other occurrences of "kit" in enderchest-related labels
(notably the occurrence around line 387 mentioned in the comment) and replace
them with the appropriate "enderchest" wording so the menu copy matches the
enderchest flow.

---

Nitpick comments:
In `@src/main/java/dev/noah/perplayerkit/gui/GUI.java`:
- Around line 32-40: These legacy state helper methods (setInspectTarget,
getAndRemoveInspectTarget, removeKitDeletionFlag) currently no-op and silently
hide callers like KitMenuCloseListener; update them to explicitly fail fast by
throwing an UnsupportedOperationException (or at minimum log a clear warning and
throw) with a message directing callers to the new API/flow so surviving callers
are surfaced during testing, or alternatively implement the original behavior if
backward compatibility must be preserved; ensure the exception message names the
method (e.g., "GUI.setInspectTarget") and references KitMenuCloseListener
migration guidance.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9690b1f1-d594-404d-9794-884b4beec603

📥 Commits

Reviewing files that changed from the base of the PR and between 1389868 and 270a08f.

📒 Files selected for processing (7)
  • src/main/java/dev/noah/perplayerkit/PerPlayerKit.java
  • src/main/java/dev/noah/perplayerkit/commands/EnderchestCommand.java
  • src/main/java/dev/noah/perplayerkit/gui/GUI.java
  • src/main/java/dev/noah/perplayerkit/gui/configurable/ConfigurableGuiService.java
  • src/main/java/dev/noah/perplayerkit/gui/configurable/GuiConfigManager.java
  • src/main/java/dev/noah/perplayerkit/gui/configurable/GuiContext.java
  • src/main/resources/guis.yml

Comment thread src/main/java/dev/noah/perplayerkit/gui/configurable/ConfigurableGuiService.java Outdated
Comment thread src/main/java/dev/noah/perplayerkit/gui/configurable/ConfigurableGuiService.java Outdated
Comment thread src/main/resources/guis.yml Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/dev/noah/perplayerkit/gui/GUI.java`:
- Around line 85-90: The method ViewPublicKitMenu currently returns a
possibly-null Menu from ConfigurableGuiService.createPublicKitViewer; make this
contract explicit by annotating the method return type with a nullable
annotation (e.g., `@Nullable` from javax.annotation or org.jetbrains.annotations)
and add the corresponding import, keeping the current behavior (open if
non-null, return value unchanged). Reference: the method ViewPublicKitMenu, the
call to ConfigurableGuiService.createPublicKitViewer(Player, String), and the
Menu/Player types so reviewers can locate and update the signature and imports
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2dbd2f51-7acb-48c8-b2ea-f666affffc07

📥 Commits

Reviewing files that changed from the base of the PR and between 270a08f and 8e574ef.

📒 Files selected for processing (3)
  • src/main/java/dev/noah/perplayerkit/PerPlayerKit.java
  • src/main/java/dev/noah/perplayerkit/commands/kits/EnderchestCommand.java
  • src/main/java/dev/noah/perplayerkit/gui/GUI.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/dev/noah/perplayerkit/PerPlayerKit.java

Comment thread src/main/java/dev/noah/perplayerkit/gui/GUI.java Outdated
@rossnoah rossnoah force-pushed the t3code/configurable-gui-system-redesign branch from 8e574ef to 1fe3d84 Compare March 13, 2026 02:01

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/main/java/dev/noah/perplayerkit/gui/configurable/ConfigurableGuiService.java (2)

912-922: Consider caching PlaceholderAPI availability check.

Bukkit.getPluginManager().getPlugin("PlaceholderAPI") is called on every text resolution. Since plugin presence doesn't change at runtime, caching this once during initialization would improve performance.

Proposed enhancement

Add a field and initialize it in the constructor:

private final boolean hasPlaceholderApi;

public ConfigurableGuiService(Plugin plugin) {
    this.plugin = plugin;
    this.guiConfigManager = new GuiConfigManager(plugin);
    this.hasPlaceholderApi = Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null;
    instance = this;
}

Then use it in resolveText:

     private String resolveText(String value, Player viewer, GuiContext context) {
         if (value == null) {
             return null;
         }

         String resolved = resolvePlainValue(value, viewer, context);
-        if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
+        if (hasPlaceholderApi) {
             resolved = PlaceholderAPI.setPlaceholders(viewer, resolved);
         }
         return StyleManager.convertMiniMessage(resolved);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/dev/noah/perplayerkit/gui/configurable/ConfigurableGuiService.java`
around lines 912 - 922, The resolveText method repeatedly calls
Bukkit.getPluginManager().getPlugin("PlaceholderAPI") on every invocation; add a
private final boolean field hasPlaceholderApi to the ConfigurableGuiService
class, initialize it once in the constructor (ConfigurableGuiService(...)) by
checking Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null, and then
replace the runtime check in resolveText with a check of hasPlaceholderApi so
PlaceholderAPI.setPlaceholders(viewer, resolved) is only called when
hasPlaceholderApi is true.

924-935: Prefer StringBuilder over StringBuffer for single-threaded code.

StringBuffer is synchronized, adding unnecessary overhead. Since this method runs on the main thread per call, StringBuilder is more appropriate.

Proposed fix
     private String resolvePlainValue(String value, Player viewer, GuiContext context) {
         Matcher matcher = PLACEHOLDER_PATTERN.matcher(value);
-        StringBuffer buffer = new StringBuffer();
+        StringBuilder buffer = new StringBuilder();

         while (matcher.find()) {
             String replacement = getPlaceholderValue(matcher.group(1), viewer, context);
             matcher.appendReplacement(buffer, Matcher.quoteReplacement(replacement));
         }

         matcher.appendTail(buffer);
         return buffer.toString();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/dev/noah/perplayerkit/gui/configurable/ConfigurableGuiService.java`
around lines 924 - 935, resolvePlainValue currently uses synchronized
StringBuffer via Matcher.appendReplacement/appendTail; replace it with an
unsynchronized StringBuilder to avoid unnecessary overhead: keep
PLACEHOLDER_PATTERN and the Matcher logic but change the loop to append the
literal text between matches and the replacement into a StringBuilder using
matcher.start()/matcher.end() (append the substring from the previous end index
to matcher.start(), then append the replacement from getPlaceholderValue), track
the last appended index, and after the loop append the remaining tail substring
before returning builder.toString(); update the method resolvePlainValue
accordingly.
src/main/java/dev/noah/perplayerkit/gui/configurable/GuiConfigManager.java (1)

21-26: Consider handling missing or malformed configuration more explicitly.

If guis.yml exists but contains invalid YAML, YamlConfiguration.loadConfiguration() returns an empty configuration without throwing. This could cause silent failures downstream when GUI sections are requested. Consider logging a warning when the file exists but produces an empty/invalid configuration.

Proposed enhancement
     public final void load() {
         if (!guiConfigFile.exists()) {
             plugin.saveResource("guis.yml", false);
         }
         guiConfiguration = YamlConfiguration.loadConfiguration(guiConfigFile);
+        if (guiConfiguration.getKeys(false).isEmpty()) {
+            plugin.getLogger().warning("guis.yml appears to be empty or invalid");
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/dev/noah/perplayerkit/gui/configurable/GuiConfigManager.java`
around lines 21 - 26, The load() method currently calls
YamlConfiguration.loadConfiguration(guiConfigFile) but doesn’t detect when the
file exists yet yields an empty/invalid configuration; update load() to check
the loaded guiConfiguration for emptiness (e.g., no top-level keys/sections)
after YamlConfiguration.loadConfiguration(guiConfigFile) and if empty: log a
warning via the plugin logger including the file name (guiConfigFile) and that
the YAML appears invalid, then either attempt to restore the default with
plugin.saveResource("guis.yml", true) or copy the bundled default into place and
re-load; reference the existing symbols load(), guiConfigFile, guiConfiguration,
plugin.saveResource and YamlConfiguration.loadConfiguration when implementing
this check and recovery.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@src/main/java/dev/noah/perplayerkit/gui/configurable/ConfigurableGuiService.java`:
- Around line 912-922: The resolveText method repeatedly calls
Bukkit.getPluginManager().getPlugin("PlaceholderAPI") on every invocation; add a
private final boolean field hasPlaceholderApi to the ConfigurableGuiService
class, initialize it once in the constructor (ConfigurableGuiService(...)) by
checking Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null, and then
replace the runtime check in resolveText with a check of hasPlaceholderApi so
PlaceholderAPI.setPlaceholders(viewer, resolved) is only called when
hasPlaceholderApi is true.
- Around line 924-935: resolvePlainValue currently uses synchronized
StringBuffer via Matcher.appendReplacement/appendTail; replace it with an
unsynchronized StringBuilder to avoid unnecessary overhead: keep
PLACEHOLDER_PATTERN and the Matcher logic but change the loop to append the
literal text between matches and the replacement into a StringBuilder using
matcher.start()/matcher.end() (append the substring from the previous end index
to matcher.start(), then append the replacement from getPlaceholderValue), track
the last appended index, and after the loop append the remaining tail substring
before returning builder.toString(); update the method resolvePlainValue
accordingly.

In `@src/main/java/dev/noah/perplayerkit/gui/configurable/GuiConfigManager.java`:
- Around line 21-26: The load() method currently calls
YamlConfiguration.loadConfiguration(guiConfigFile) but doesn’t detect when the
file exists yet yields an empty/invalid configuration; update load() to check
the loaded guiConfiguration for emptiness (e.g., no top-level keys/sections)
after YamlConfiguration.loadConfiguration(guiConfigFile) and if empty: log a
warning via the plugin logger including the file name (guiConfigFile) and that
the YAML appears invalid, then either attempt to restore the default with
plugin.saveResource("guis.yml", true) or copy the bundled default into place and
re-load; reference the existing symbols load(), guiConfigFile, guiConfiguration,
plugin.saveResource and YamlConfiguration.loadConfiguration when implementing
this check and recovery.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 93237830-abcf-436c-9a09-d1d43b4d6004

📥 Commits

Reviewing files that changed from the base of the PR and between 8e574ef and 1fe3d84.

📒 Files selected for processing (7)
  • src/main/java/dev/noah/perplayerkit/PerPlayerKit.java
  • src/main/java/dev/noah/perplayerkit/commands/kits/EnderchestCommand.java
  • src/main/java/dev/noah/perplayerkit/gui/GUI.java
  • src/main/java/dev/noah/perplayerkit/gui/configurable/ConfigurableGuiService.java
  • src/main/java/dev/noah/perplayerkit/gui/configurable/GuiConfigManager.java
  • src/main/java/dev/noah/perplayerkit/gui/configurable/GuiContext.java
  • src/main/resources/guis.yml
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/resources/guis.yml
  • src/main/java/dev/noah/perplayerkit/gui/GUI.java
  • src/main/java/dev/noah/perplayerkit/PerPlayerKit.java
  • src/main/java/dev/noah/perplayerkit/gui/configurable/GuiContext.java

rossnoah and others added 6 commits July 10, 2026 21:25
The configurable GUI engine (added next) lives in gui.configurable and
needs the title-update capability check and in-place title updates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bukkit-free building blocks for the configurable GUI engine:
- GuiSlots parses slot declarations (numbers, ranges incl. descending,
  lists), order-preserving and bounds-checked against the menu size
- GuiText expands %lang:<key>% (lang lookup applies the message's own
  {token} placeholders) and %token% context values, leaving unknown
  tokens for PlaceholderAPI; resolveInt expands placeholders in numeric
  arguments so "slot: %slot%" means the context slot
- GuiContext is the immutable value bag menus are opened with

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Menus missing from the user's guis.yml fall back to the copy bundled in
the jar, per GUI (not per key), so shipping new menus never strands old
installs. A file with an older config-version is archived to
guis.yml.v<N>.bak and regenerated with a warning: a schema bump can
rename components or actions, and a stale file would otherwise shadow
the bundled menus wholesale and half-work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The kit room GUI is about to become config-driven; callers need the
storage bound to clamp page indexes instead of running into the fixed
five-page list's IndexOutOfBoundsException.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ConfigurableGuiService builds every menu from guis.yml: elements carry
slots, items, permissions, and click actions; components (kit-slot
selectors, pagination arrows, public-kit list, item-data regions, kit
room categories) render plugin data into config-declared positions;
variants give per-state items/actions with a named -> default ->
element fallback per key. All default text routes through the lang
system via %lang:key% placeholders, so the bundled translations apply
and owners can restyle any menu; PlaceholderAPI works where installed.
Repeated menu blocks (armor indicators, editor tools) are shared as
YAML anchors, covered by a test proving YamlConfiguration flattens
merge keys.

Editor persistence keeps the session model the old GUI class proved
out: an EditorSession registered on open records the data slots (from
the menu's own config) and expected size; it is flushed by
InventoryCloseEvent or before opening the next menu, since canvas
redraw navigation reuses the open inventory and never fires a close
event. Saves read the closing Bukkit inventory directly. Delete
actions suppress the close-save even when the delete found nothing —
the menu still shows stale contents and saving them would resurrect a
kit another admin deleted.

max-kits pagination is a main-menu component: kit slot numbers derive
from the page in the open context, arrows appear only where valid, and
back buttons return to the page a kit lives on (or the remembered
page), while /kit always starts at page 1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GUI becomes a thin facade over ConfigurableGuiService, keeping the
entry points commands and listeners call; KitMenuCloseListener hands
closing inventories to the service's editor sessions; the view-only
enderchest and plugin startup wire in the service. EditorSaver,
GuiMenuFactory, and GuiLayoutUtils are superseded by the engine and
guis.yml.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rossnoah rossnoah force-pushed the t3code/configurable-gui-system-redesign branch from 1fe3d84 to 850f224 Compare July 11, 2026 05:08
@rossnoah rossnoah changed the title Add configurable GUI service and migrate menu flows to YAML-driven definitions Rebuild GUI menus on a config-driven engine (guis.yml) Jul 11, 2026
@rossnoah

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.


Your plan includes PR reviews subject to rate limits. More reviews will be available in 58 minutes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/test/java/dev/noah/perplayerkit/gui/configurable/BundledGuisConfigTest.java (1)

27-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Close the resource stream after loading the YAML.

The InputStream from getResourceAsStream is wrapped in an InputStreamReader but never closed. While the JVM will clean up at test shutdown, using try-with-resources is best practice and prevents resource leak warnings in CI.

♻️ Proposed refactor
     `@BeforeAll`
     static void load() {
-        var stream = BundledGuisConfigTest.class.getClassLoader().getResourceAsStream("guis.yml");
-        assertNotNull(stream, "guis.yml missing from resources");
-        config = YamlConfiguration.loadConfiguration(new InputStreamReader(stream, StandardCharsets.UTF_8));
+        try (var stream = BundledGuisConfigTest.class.getClassLoader().getResourceAsStream("guis.yml")) {
+            assertNotNull(stream, "guis.yml missing from resources");
+            config = YamlConfiguration.loadConfiguration(new InputStreamReader(stream, StandardCharsets.UTF_8));
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/dev/noah/perplayerkit/gui/configurable/BundledGuisConfigTest.java`
around lines 27 - 32, Update the `@BeforeAll` load() method to wrap the resource
InputStreamReader in try-with-resources, ensuring the stream from
getResourceAsStream is closed after YamlConfiguration.loadConfiguration
completes while preserving the existing missing-resource assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/dev/noah/perplayerkit/gui/configurable/ConfigurableGuiService.java`:
- Around line 487-508: Implement handling for public kits beyond the configured
slot count in renderPublicKitList: either add pagination/navigation so every kit
can be loaded and viewed, or explicitly reject configurations where publicKits
exceeds slots and prevent startup/use with a clear validation error. Ensure the
chosen behavior covers load, view, and edit access rather than only logging a
warning.

In `@src/main/java/dev/noah/perplayerkit/gui/configurable/GuiSlots.java`:
- Around line 88-91: Limit range expansion in the slot-parsing logic before
entering the loop in GuiSlots:parse (or the helper containing the shown loop),
rejecting ranges whose size exceeds a safe maximum. Calculate the range length
using long arithmetic to avoid overflow, validate both endpoints and the
computed size, then iterate only after validation so oversized or overflowing
ranges cannot populate the LinkedHashSet.

---

Nitpick comments:
In
`@src/test/java/dev/noah/perplayerkit/gui/configurable/BundledGuisConfigTest.java`:
- Around line 27-32: Update the `@BeforeAll` load() method to wrap the resource
InputStreamReader in try-with-resources, ensuring the stream from
getResourceAsStream is closed after YamlConfiguration.loadConfiguration
completes while preserving the existing missing-resource assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 339af013-4d66-4d8d-8124-78b9587a33f4

📥 Commits

Reviewing files that changed from the base of the PR and between 1fe3d84 and 850f224.

📒 Files selected for processing (18)
  • src/main/java/dev/noah/perplayerkit/KitRoomDataManager.java
  • src/main/java/dev/noah/perplayerkit/PerPlayerKit.java
  • src/main/java/dev/noah/perplayerkit/commands/kits/EnderchestCommand.java
  • src/main/java/dev/noah/perplayerkit/gui/GUI.java
  • src/main/java/dev/noah/perplayerkit/gui/GuiCompat.java
  • src/main/java/dev/noah/perplayerkit/gui/GuiLayoutUtils.java
  • src/main/java/dev/noah/perplayerkit/gui/GuiMenuFactory.java
  • src/main/java/dev/noah/perplayerkit/gui/configurable/ConfigurableGuiService.java
  • src/main/java/dev/noah/perplayerkit/gui/configurable/EditorSession.java
  • src/main/java/dev/noah/perplayerkit/gui/configurable/GuiConfigManager.java
  • src/main/java/dev/noah/perplayerkit/gui/configurable/GuiContext.java
  • src/main/java/dev/noah/perplayerkit/gui/configurable/GuiSlots.java
  • src/main/java/dev/noah/perplayerkit/gui/configurable/GuiText.java
  • src/main/java/dev/noah/perplayerkit/listeners/KitMenuCloseListener.java
  • src/main/resources/guis.yml
  • src/test/java/dev/noah/perplayerkit/gui/configurable/BundledGuisConfigTest.java
  • src/test/java/dev/noah/perplayerkit/gui/configurable/GuiSlotsTest.java
  • src/test/java/dev/noah/perplayerkit/gui/configurable/GuiTextTest.java
💤 Files with no reviewable changes (2)
  • src/main/java/dev/noah/perplayerkit/gui/GuiMenuFactory.java
  • src/main/java/dev/noah/perplayerkit/gui/GuiLayoutUtils.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/dev/noah/perplayerkit/commands/kits/EnderchestCommand.java
  • src/main/resources/guis.yml

Comment thread src/main/java/dev/noah/perplayerkit/gui/configurable/GuiSlots.java
- GuiSlots rejects ranges wider than any menu before expanding them; a
  typo like "0-999999999" warned per-slot after ballooning the set (and
  could overflow at the int boundary) instead of being dropped outright
- The public kit list gets 27 slots (the row above was filler glass);
  kits beyond the configured slots are still capped with a warning —
  the old code rendered them over the footer buttons instead
- The enderchest editor's clear button says CLEAR ENDERCHEST, not
  CLEAR KIT (label carried over from the kit editor, also flagged on
  the original branch)
- Close the guis.yml resource stream in the bundled-config test

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant